#ifndef VIZMF_CAMERA_H
#define VIZMF_CAMERA_H

#include "vector3.h"
#include "vector4.h"
#include "matrix4.h"
#include "material.h"

#include <boost/math/quaternion.hpp>
#include <cmath>

class Camera
{
public:
  Camera(const Vector3& lookAt, const double r) 
    : fov(60.0), up(Vector3::Y()), right(Vector3::X()), bwd(Vector3::Z()),
      target(lookAt), dist(r), moveSpd(0.1), rotSpd(1.0), invX(1), invY(1)
  {
    Material c(0.2, 0.2, 0.2, 1.0);
    c.setDiffuse(1.0, 1.0, 1.0, 1.0);
    color_ = c;
  }
  void setTarget(const Vector3& t) {target = t;}
  void setDistance(const float d) {dist = d;}
  Vector3 pos() const { return (target + bwd*dist); }
  double moveSpeed() const { return moveSpd; }
  double rotSpeed() const { return rotSpd; }
  double setMoveSpeed(const double spd) { return moveSpd = spd; }
  double setRotSpeed(const double spd) { return rotSpd = spd; }
  bool invertX() const { return (invX == -1) ? true : false; }
  bool invertY() const { return (invY == -1) ? true : false; }
  void setInvertX(bool f) { invX = (f) ? -1 : 1; }
  void setInvertY(bool f) { invY = (f) ? -1 : 1; }
  Matrix4 view() const
  {
    Matrix4 result;
    result(0, 0) = right.x(); result(0, 1) = right.y(); result(0, 2) = right.z(); result(0, 3) = -right.dot_prod(pos());
    result(1, 0) = up.x()   ; result(1, 1) = up.y()   ; result(1, 2) = up.z()   ; result(1, 3) = -up.dot_prod(pos());
    result(2, 0) = bwd.x()  ; result(2, 1) = bwd.y()  ; result(2, 2) = bwd.z()  ; result(2, 3) = -bwd.dot_prod(pos());
    result(3, 0) = 0.0      ; result(3, 1) = 0.0      ; result(3, 2) = 0.0      ; result(3, 3) = 1.0;
    return result;
  }
  void zoom(int dz)
  {
    dist *= (1+dz*moveSpd);
  }
  void rotate(int dx, int dy)
  {
/*    double ctx = cos(dx*rotSpd*invX), stx = sin(dx*rotSpd*invX),
           cty = cos(dy*rotSpd*invY), sty = sin(dy*rotSpd*invY);
    typedef boost::math::quaternion<float> quat;
    quat qx(ctx, up.x()*stx, up.y()*stx, up.z()*stx),
         qy(cty, right.x()*sty, right.y()*sty, right.z()*sty);
    quat rot = qy*qx;
    quat rot_conj = quat(rot.R_component_1(), -rot.R_component_2(), -rot.R_component_3(), -rot.R_component_4());
    quat qup(0.0, up.x(), up.y(), up.z()), 
         qright(0.0, right.x(), right.y(), right.z()), 
         qbwd(0.0, bwd.x(), bwd.y(), bwd.z());
    qup = rot*qup*rot_conj;
    qright = rot*qright*rot_conj;
    qbwd = rot*qbwd*rot_conj;
    up = Vector3(qup.R_component_2(), qup.R_component_3(), qup.R_component_4());
    right = Vector3(qright.R_component_2(), qright.R_component_3(), qright.R_component_4());
    bwd = Vector3(qbwd.R_component_2(), qbwd.R_component_3(), qbwd.R_component_4()); */
    Matrix4 rot;
    rot.loadIdentity();
    rot.rotate(up, float(dx)*rotSpd*float(invX)/360.0*3.1415);
    rot.rotate(right, float(dy)*rotSpd*float(invY)/360.0*3.1415);
    up = rot*up;
    right = rot*right;
    bwd = rot*bwd;
    up.normalize();
    right.normalize();
    bwd.normalize();
  }
  Vector3 lightPos() const 
  {
    return pos() + up - right;
  }
  const Material& lightColor() const
  {
    return color_;
  }
  Matrix4 projection(float aspect, float near = 0.1, float far = 1000.0) const
  {
    return Matrix4(fov, aspect, near, far);
  }
  void setFOV(double v) { fov = v; } 
private:
  double fov;
  Vector3 up, right, bwd, target;
  double dist, moveSpd, rotSpd;
  int invX, invY;
  Material color_;
};

#endif
